using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MySqlX.XDevAPI;
using OpenIddict.EntityFrameworkCore.Models;
using SageKing.Database.Contracts;

namespace SageKing.OpenIddict.UI.Api;

public interface IApplicationApiService : IEntityPage<OpenIddictEntityFrameworkCoreApplication, ApplicationViewModel>
{
    public Task<IEnumerable<ApplicationViewModel>> GetApplicationsAsync();

    public Task<ApplicationViewModel> GetAsync(string clientId);

    public Task<string> CreateAsync(ApplicationViewModel model);

    public Task UpdateAsync(ApplicationViewModel model);

    public Task DeleteAsync(string clientId);

    public Task<ApplicationOptionsViewModel> GetOptionsAsync();
}

public class ApplicationApiService : IApplicationApiService
{
    private readonly IApplicationService _service;
    private readonly IOptions<OpenIddictUIApiOptions> _options;

    public ApplicationApiService(
      IApplicationService service,
      IOptions<OpenIddictUIApiOptions> options
    )
    {
        _service = service
          ?? throw new ArgumentNullException(nameof(service));
        _options = options
          ?? throw new ArgumentNullException(nameof(options));
    }

    public async Task<IEnumerable<ApplicationViewModel>> GetApplicationsAsync()
    {
        var items = await _service.GetApplicationsAsync();

        return items.Select(ToModel);
    }

    public async Task<ApplicationViewModel> GetAsync(string clientId)
    {
        ArgumentNullException.ThrowIfNull(clientId);

        var claimType = await _service.GetAsync(clientId);

        return claimType != null ? ToModel(claimType) : null;
    }

    public async Task<string> CreateAsync(ApplicationViewModel model)
    {
        ArgumentNullException.ThrowIfNull(model);

        var param = ToParam(model);

        return await _service.CreateAsync(param);
    }

    public async Task UpdateAsync(ApplicationViewModel model)
    {
        ArgumentNullException.ThrowIfNull(model);

        if (string.IsNullOrWhiteSpace(model.Id))
        {
            throw new InvalidOperationException(nameof(model.Id));
        }

        var param = ToParam(model);

        await _service.UpdateAsync(param);
    }

    public async Task DeleteAsync(string clientId)
    {
        ArgumentNullException.ThrowIfNull(clientId);

        await _service.DeleteAsync(clientId);
    }

    public async Task<ApplicationOptionsViewModel> GetOptionsAsync()
    {
        var options = _options.Value;

        var model = new ApplicationOptionsViewModel
        {
            Permissions = options.Permissions
        };

        return await Task.FromResult(model);
    }

    private static ApplicationParam ToParam(ApplicationViewModel model)
    {
        return new ApplicationParam
        {
            Id = model.Id,
            ClientId = model.ClientId,
            DisplayName = model.DisplayName,
            ClientSecret = model.ClientSecret,
            Type = model.Type,
            RequirePkce = model.RequirePkce,
            RequireConsent = model.RequireConsent,
            Permissions = model.Permissions,
            RedirectUris = model.RedirectUris,
            PostLogoutRedirectUris = model.PostLogoutRedirectUris
        };
    }

    private static ApplicationViewModel ToModel(ApplicationInfo info)
    {
        return new ApplicationViewModel
        {
            Id = info.Id,
            ClientId = info.ClientId,
            DisplayName = info.DisplayName,
            Type = info.Type,
            RequirePkce = info.RequirePkce,
            RequireConsent = info.RequireConsent,
            Permissions = info.Permissions,
            RedirectUris = info.RedirectUris,
            PostLogoutRedirectUris = info.PostLogoutRedirectUris
        };
    }

    public async Task<PageBase<ApplicationViewModel>> GetPageAsync(PageBaseInput page)
    {
        var result = await _service.GetPageAsync(page);
        var getItems = result.Items.Select(ToModel);
        return result.GetPageBase(getItems);
    }

    public async Task<PageBase<ApplicationViewModel>> GetPage(PageBaseInput page, Expression<Func<OpenIddictEntityFrameworkCoreApplication, object>> orderby, bool orderByAsc = true)
    {
        var result = await _service.GetPage(page, orderby, orderByAsc);
        var getItems = result.Items.Select(ToModel);
        return result.GetPageBase(getItems);
    }

    public async Task<PageBase<ApplicationViewModel>> GetPage(PageBaseInput page, List<(bool, Expression<Func<OpenIddictEntityFrameworkCoreApplication, bool>>)> whereIf, Expression<Func<OpenIddictEntityFrameworkCoreApplication, object>> orderby, bool orderByAsc = true)
    {
        var result = await _service.GetPage(page, whereIf, orderby, orderByAsc);
        var getItems = result.Items.Select(ToModel);
        return result.GetPageBase(getItems);
    }
}
